﻿using System;
using System.Collections;
using System.Collections.Generic;

using OCXS;
using OCXS.OCXSServiceLibrary.OPCXMLDA10;

namespace OCXS {

	namespace OCXSServiceLibrary {
        
		public partial class OCXSService {

            /// <summary>
            /// Minimum Subscription sampling rate supported (in milliseconds).
            /// </summary>
            public const int MinSubscriptionSamplingRate = 20;

            /// <summary>
            /// Default Subscription sampling rate supported (in milliseconds).
            /// </summary>
            public const int DefaultSubscriptionSamplingRate = 100;

			/// <summary>
            /// <para>
            /// Implements OPC XML DA 1.0 Subscribe service. 
            /// </para>
            /// <para>
            /// See OPC XML DA 1.0 specification for more information about Subscribe service.
            /// </para>
			/// </summary>
            /// <remarks>
            /// <para>
            ///     <b>Following there's a list of implemented (or not) standard features.</b>
            /// </para>
            /// <para>
            ///     The following <c>SubscribeRequest</c> standard features are (or not) implemented.
            /// 
            ///     <ul>
            ///         <li>
            ///             <b>ItemList: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>Deadband: </b> not supported (OPC Item specific deadbands supported instead).</li>
            ///                 <li><b>DeadbandSpecified: </b> ignored.</li>
            ///                 <li>
            ///                     <b>EnableBuffering: </b> ignored (Buffering is natively supported by OCXS Service Implementation).
            ///                     Please consider that OCXS Service for each subscribed Item allocates a buffer, but only Last Changed
            ///                     Values (LCVs) are returned.
            ///                 </li>
            ///                 <li><b>EnableBufferingSpecified: </b> ignored.</li>
            ///                 <li><b>ItemPath: </b> ignored (OPC Item specific deadbands supported instead).</li>
            ///                 <li>
            ///                     <b>Items: </b> the following features are (or not) supported:
            ///                     <ul>
            ///                         <li><b>ClientItemHandle: </b> supported.</li>
            ///                         <li><b>Deadband: </b> supported.</li>
            ///                         <li><b>DeadbandSpecified: </b> supported.</li>
            ///                         <li><b>EnableBuffering: </b> see EnableBuffering above.</li>
            ///                         <li><b>EnableBufferingSpecified: </b> ignored.</li>
            ///                         <li><b>ItemName: </b> supported.</li>
            ///                         <li><b>ItemPath: </b> supported.</li>
            ///                         <li><b>ReqType: </b> not supported.</li>
            ///                         <li>
            ///                             <b>RequestedSamplingRate: </b> not supported (OPC Subscription-level sampling rate supported instead). As direct consequence, all subscribed Items in a subscription share the same sampling rate.
            ///                         </li>
            ///                         <li><b>RequestedSamplingRateSpecified: </b> ignored.</li>
            ///                     </ul>
            ///                 </li>
            ///                 <li><b>ReqType: </b> not supported.</li>
            ///                 <li><b>RequestedSamplingRate: </b> supported.</li>
            ///                 <li><b>RequestedSamplingRateSpecified: </b> supported.</li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li>
            ///             <b>Options: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>LocaleID: </b> supported.</li>
            ///                 <li><b>RequestDeadline: </b> not supported.</li>
            ///                 <li><b>RequestDeadlineSpecified: </b> ignored.</li>
            ///                 <li><b>ReturnDiagnosticInfo: </b> supported.</li>
            ///                 <li><b>ReturnErrorText: </b> supported.</li>
            ///                 <li><b>ReturnItemName: </b> supported.</li>
            ///                 <li><b>ReturnItemPath: </b> supported.</li>
            ///                 <li><b>ReturnItemTime: </b> supported.</li>
            ///             </ul>
            ///         </li>
            ///         <li><b>ReturnValuesOnReply: </b> supported.</li>
            ///         <li><b>SubscriptionPingRate: </b> supported.</li>
            ///     </ul>
            /// </para>
            /// 
            /// <para>
            ///     The following <c>SubscribeResponse</c> standard features are (or not) implemented.
            ///     <ul>
            ///         
            ///         <li>
            ///             <b>Errors: </b> One error per time is reported. The following errors are supported:
            ///             <ul>
            ///                 <li><c>E_OUTOFMEMORY</c></li>
            ///                 <li><c>E_FAIL</c></li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li>
            ///             <b>RItemList: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li>
            ///                     <b>Items: </b> the following features are (or not) supported:
            ///                     <ul>
            ///                         <li>
            ///                             <b>ItemValue: </b> the following features are (or not) supported:
            ///                             <ul>
            ///                                 <li><b>ClientItemHandle: </b> supported.</li>
            ///                                 <li><b>DiagnosticInfo: </b> supported.</li>
            ///                                 <li><b>ItemName: </b> supported.</li>
            ///                                 <li><b>ItemPath: </b> supported.</li>
            ///                                 <li><b>Quality: </b> supported (static values: <c>good</c>).</li>
            ///                                 <li><b>ResultID: </b> supported. Only <c>E_UNKNOWNITEMNAME</c> error is supported.</li>
            ///                                 <li><b>Timestamp: </b> supported.</li>
            ///                                 <li><b>TimestampSpecified: </b> supported</li>
            ///                                 <li><b>Value: </b> supported.</li>
            ///                                 <li><b>ValueTypeQualifier: </b> supported (<c>xs:float</c>).</li>
            ///                             </ul>
            ///                         </li>
            ///                         
            ///                         <li><b>RevisedSamplingRate: </b> not supported (Subscription-level sampling rate is considred instead).</li>
            ///                         <li><b>RevisedSamplingRateSpecified: </b> ignored.</li>
            ///                     </ul>
            ///                 </li>
            ///                 <li><b>RevisedSamplingRate: </b> supported.</li>
            ///                 <li><b>RevisedSamplingRateSpecified: </b> supported.</li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li><b>ServerSubHandle: </b> supported.</li>
            /// 
            ///         <li>
            ///             <b>SubscribeResult: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>RcvTime: </b> supported (evaluated as first instruction).</li>
            ///                 <li><b>ReplyTime: </b> supported (evaluated as last instruction).</li>
            ///                 <li><b>RevisedLocaleID: </b> supported (<c>en-us</c>).</li>
            ///                 <li><b>ServerState: </b> supported (<c>running</c>).</li>
            ///             </ul>
            ///         </li>
            ///     </ul>
            /// </para>
            /// 
            /// <code>
            /// // The following code can be found in OCXSClient and 
            /// // shows how to call Subscribe service.
            /// class Program {
            ///
            ///     private const string EndpointAddress = "http://localhost/OCXS/OCXS.svc";
            ///     
            ///     static void Main(string[] args) {
            ///     
            ///     // In the response subscribed Item values will be returned.
            ///     // The subscription expires after 10 seconds of inactivity.
            ///     SubscribeResponse sr = Subscribe("en-us", "0x00FF", true, 10000);
            ///     
            ///     // After 10 seconds, as specified in the Subscribe, the subscription 
            ///     // will be cancelled due to timeout.
            /// }
            ///    
            /// public static SubscribeResponse Subscribe(
            /// 	string localeID,
            /// 	string clientRequestHandle,
            /// 	bool returnValues,
            /// 	int pingRate,
            /// 	bool returnItemName = true,
            /// 	bool returnItemPath = true,
            /// 	bool returnItemTime = true,
            /// 	bool returnDiagnosticInfo = false,
            /// 	bool returnErrorText = false,
            /// 	SubscribeRequestItemList requestItemList = null,
            /// 	// Not supported features down here
            /// 	bool requestDeadlineSpecified = false)
            /// {
            /// 	// Get the channel.
            /// 	// GetOCXSChannel can be found in OCXSClient.
            /// 	IOCXSService svc = GetOCXSChannel(EndpointAddress);
            /// 
            /// 	// Create request options, for ReadRequest.
            /// 	RequestOptions ro = new RequestOptions();
            /// 	ro.ClientRequestHandle = clientRequestHandle;
            /// 	ro.LocaleID = localeID;
            /// 	ro.RequestDeadline = DateTime.Parse("2001-09-11");
            /// 	ro.RequestDeadlineSpecified = requestDeadlineSpecified;
            /// 	ro.ReturnDiagnosticInfo = returnDiagnosticInfo;
            /// 	ro.ReturnErrorText = returnErrorText;
            /// 	ro.ReturnItemName = returnItemName;
            /// 	ro.ReturnItemPath = returnItemPath;
            /// 	ro.ReturnItemTime = returnItemTime;
            /// 
            /// 	SubscribeRequestItemList requestItems = requestItemList;
            /// 
            /// 	// Let's create a valid SubscribeRequestItemList if not provided.
            /// 	// This way is shown how to create a valid ReadRequestItemList.
            /// 	if (requestItems == null) {
            /// 		requestItems = new SubscribeRequestItemList();
            /// 
            /// 		// RequestedSamplingRate will be valid for all items in
            /// 		// this subscription.
            /// 		requestItems.RequestedSamplingRate = 1000;
            /// 		requestItems.RequestedSamplingRateSpecified = true;
            /// 
            /// 
            /// 		// requestItems.Items is a SubscribeRequestItem[].
            /// 		// There are two 'main ways' to obtain an SubscribeRequestItem[]:
            /// 		//   1. Creating a static fixed size array.
            /// 		//
            /// 		//   2. Create a List&lt;SubscribeRequestItem&gt; and call ToArray() method
            /// 		//      when finished adding ItemValues.
            /// 		//
            /// 		// The solution (1) follows.
            /// 		requestItems.Items = new SubscribeRequestItem[2];
            /// 
            /// 		requestItems.Items[0] = new SubscribeRequestItem();
            /// 		requestItems.Items[0].ClientItemHandle = clientRequestHandle;
            /// 		requestItems.Items[0].Deadband = 0.5f;
            /// 		requestItems.Items[0].DeadbandSpecified = true;
            /// 		requestItems.Items[0].ItemName = "MyVar4";
            /// 		requestItems.Items[0].ItemPath = "Root/Fold2/Fold3/";
            /// 		// The following are not supported features.
            /// 		requestItems.Items[0].EnableBuffering = false;
            /// 		requestItems.Items[0].EnableBufferingSpecified = false;
            /// 		requestItems.Items[0].ReqType = null;
            /// 		requestItems.Items[0].RequestedSamplingRate = 0;
            /// 		requestItems.Items[0].RequestedSamplingRateSpecified = false;
            /// 
            /// 		requestItems.Items[1] = new SubscribeRequestItem();
            /// 		requestItems.Items[1].ClientItemHandle = clientRequestHandle;
            /// 		requestItems.Items[1].Deadband = 0.5f;
            /// 		requestItems.Items[1].DeadbandSpecified = true;
            /// 		requestItems.Items[1].ItemName = "MyVar42";
            /// 		requestItems.Items[1].ItemPath = "Root/Fold2/Fold3/";
            /// 		// The following are not supported features.
            /// 		requestItems.Items[1].EnableBuffering = false;
            /// 		requestItems.Items[1].EnableBufferingSpecified = false;
            /// 		requestItems.Items[1].ReqType = null;
            /// 		requestItems.Items[1].RequestedSamplingRate = 0;
            /// 		requestItems.Items[1].RequestedSamplingRateSpecified = false;
            /// 
            /// 		// The following are not supported features.
            /// 		requestItems.Deadband = 0.0f;
            /// 		requestItems.DeadbandSpecified = false;
            /// 		requestItems.EnableBuffering = false;
            /// 		requestItems.EnableBufferingSpecified = false;
            /// 		requestItems.ItemPath = null;
            /// 		requestItems.ReqType = null;
            /// 	}
            /// 
            /// 	SubscribeRequest SR = new SubscribeRequest(
            /// 		ro,
            /// 		requestItems,
            /// 		returnValues,
            /// 		pingRate);
            /// 
            ///     // Perform the call to service and get the response.
            /// 	SubscribeResponse res = svc.Subscribe(SR);
            /// 
            ///     // Print the response.
            ///     // PrintResponse can be found in OCXSClient.
            /// 	PrintResponse(res);
            /// 
            /// 	return res;
            /// }
            /// </code>
            /// 
            /// </remarks>
            /// <param name="request">The <c>SubscribeRequest</c> issued by client according to OCP XML DA 1.0 specification.</param>
            /// <returns>A <c>SubscribeResponse</c> according to OPC XML DA 1.0 specification.</returns>
			public SubscribeResponse Subscribe(SubscribeRequest request) {

                SubscribeResponse SR = null;
                // Get the reply base
                ReplyBase RB = this.GetReplyBase(request.Options.ClientRequestHandle, request.Options.LocaleID);

                try {

                    SubscribeReplyItemList SRIL = new SubscribeReplyItemList();

                    /* *** BEGIN handle sampling rate *** */
                    if (request.ItemList.RequestedSamplingRateSpecified)
                        SRIL.RevisedSamplingRate = (request.ItemList.RequestedSamplingRate < MinSubscriptionSamplingRate)
                            ? MinSubscriptionSamplingRate
                            : request.ItemList.RequestedSamplingRate;
                    else
                        SRIL.RevisedSamplingRate = DefaultSubscriptionSamplingRate;
                    
                    SRIL.RevisedSamplingRateSpecified = true; // Sampling rate is always specified.
                    /* *** END handle sampling rate *** */


                    /* *** BEGIN Handle variable IDs and Deadbands *** */
                    // Preparing to return ItemValues
                    SRIL.Items = new SubscribeItemValue[request.ItemList.Items.Length];
                    
                    List<string> varids = new List<string>();
                    List<float> deadbands = new List<float>();

                    for (int i = 0; i < request.ItemList.Items.Length; i++) {

                        bool gotV = this.dataStore.ContainsKey(request.ItemList.Items[i].ItemPath + request.ItemList.Items[i].ItemName);

                        if (gotV) {
                            varids.Add(request.ItemList.Items[i].ItemPath + request.ItemList.Items[i].ItemName);
                            deadbands.Add((request.ItemList.Items[i].DeadbandSpecified) ? request.ItemList.Items[i].Deadband : 0.0f);
                        } 

                        // Creating single SubscribeItemValue
                        SRIL.Items[i] = new SubscribeItemValue();
                        SRIL.Items[i].RevisedSamplingRate = SRIL.RevisedSamplingRate; // This is set above
                        SRIL.Items[i].RevisedSamplingRateSpecified = true;
                        
                        // Filling ItemValue for current SubscribeItemValue
                        if (request.ReturnValuesOnReply) {
                            SRIL.Items[i].ItemValue = new ItemValue();
                            SRIL.Items[i].ItemValue.ClientItemHandle = request.ItemList.Items[i].ClientItemHandle;
                            SRIL.Items[i].ItemValue.DiagnosticInfo = (request.Options.ReturnDiagnosticInfo) ? "Diagnostics ok! Hurray!" : null;
                            SRIL.Items[i].ItemValue.ItemPath = (request.Options.ReturnItemPath) ? request.ItemList.Items[i].ItemPath : null;
                            SRIL.Items[i].ItemValue.ItemName = (request.Options.ReturnItemName) ? request.ItemList.Items[i].ItemName : null;
                            
                            SRIL.Items[i].ItemValue.Quality = new OPCQuality();
                            SRIL.Items[i].ItemValue.Quality.QualityField = qualityBits.good;
                            SRIL.Items[i].ItemValue.Quality.LimitField = limitBits.none;
                            SRIL.Items[i].ItemValue.Quality.VendorField = 0;
                            
                            SRIL.Items[i].ItemValue.ResultID = 
                                        (gotV)
                                        ? null
                                        : new System.Xml.XmlQualifiedName("E_UNKNOWNITEMNAME", "http://opcfoundation.org/webservices/XMLDA/1.0/");
                            
                            SRIL.Items[i].ItemValue.Timestamp = (request.Options.ReturnItemTime) ? DateTime.Now : DateTime.MinValue;
                            SRIL.Items[i].ItemValue.TimestampSpecified = request.Options.ReturnItemTime;

                            SRIL.Items[i].ItemValue.Value =
                                        (gotV)
                                        ? this.GetVariableById(request.ItemList.Items[i].ItemPath, request.ItemList.Items[i].ItemName).VariableValue
                                        : null;

                        } else
                            // Otherwise return null ItemValue.
                            SRIL.Items[i].ItemValue = null;
                    }
                    /* *** END Handle variable IDs and Deadbands *** */

                    
                    /* *** BEGIN Resource allocation *** */
                    SubscriptionRecord subscriptionrec = null;

                    // One for all (RevisedSamplingRate)
                    subscriptionrec = new SubscriptionRecord(
                        varids.ToArray(),
                        this.dataStore,
                        SRIL.RevisedSamplingRate,
                        request.SubscriptionPingRate,
                        deadbands.ToArray());

                    // Adding subscription to table
                    lock (this.subscriptionTablePadlock) {
                        this.subscriptionTable.Add(subscriptionrec.SID, subscriptionrec); /* Adding to the table */
                    }
                    /* *** END Resource allocation *** */

                    /* Allocation completed, preparing response. */
                    SR = new SubscribeResponse(
                            RB, 
                            SRIL, 
                            null, // OPCError
                            subscriptionrec.SID); // New subscription identifier.

                    /* Before returning */
                    // Start PingElapse timer.
                    subscriptionrec.StartRelease();

                    // Refresh ReplyBase
                    RB.ReplyTime = DateTime.Now;

                    return SR;

                } catch (OutOfMemoryException e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new SubscribeResponse(
                            RB,
                            null,
                            new OPCError[] { this.BuildOPCError("E_OUTOFMEMORY", e.Message) },
                            null);

                } catch (Exception e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new SubscribeResponse(
                            RB,
                            null,
                            new OPCError[] { this.BuildOPCError("E_FAIL", e.Message) },
                            null);
                }

			}

		}
        
	}

}
